package com.y3k.excel.utils.exporter
{
	import com.as3xls.xls.ExcelFile;
	import com.as3xls.xls.Sheet;
	
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.navigateToURL;
	import flash.system.Capabilities;
	import flash.utils.ByteArray;
	
	import mx.charts.chartClasses.ChartBase;
	import mx.collections.ArrayCollection;
	import mx.collections.ICollectionView;
	import mx.collections.IViewCursor;
	import mx.collections.XMLListCollection;
	import mx.controls.AdvancedDataGrid;
	import mx.controls.Alert;
	import mx.controls.DataGrid;
	import mx.controls.OLAPDataGrid;
	import mx.managers.BrowserManager;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.RemoteObject;
	
	/**
	 *
	 * Clase que maneja la exportacion de elementos a Excel.
	 * 
	 * Proyectos relacionados/utilizados:
	 * Se utiliza el proyecto http://code.google.com/p/as3xls/ para la generacion de Excel.
	 * 
	 * Requisitos:
	 * Se necesita la version 10 de Flash player para realizar el correcto
	 * guardado del archivo sin pasar por backend y tener la libreria as3xls dentro
	 * del proyecto o vinculada de alguna manera.
	 * 
	 * @example Exportando desde un datagrid
	 * <listing version="3.0">
	 * private var flatData:ArrayCollection = new ArrayCollection([
	 *										{nombre:"Andrés", apellido:"Sanchez"},
	 *										{nombre:"Mónica", apellido:"Sanchez"},
	 *										{nombre:"Agustina", apellido:"Sanchez"},
	 *										{nombre:"Pablo", apellido:"Sanchez"},
	 *										{nombre:"Magalí", apellido:"Sanchez"}
	 *										]);
	 * var dg:DataGrid = new DataGrid();
	 * dg.columns = [new DataGridColumn("nombre"), new DataGridColumn("apellido")];
	 * dg.dataProvider = flatData;
	 * ExcelExport.fromGrid(
	 * 						dg, 
	 * 						"exportacion_datagrid.xls"
	 * 						);
	 * </listing>
	 * @example Exportacion general (en este caso un chart)
	 * <listing version="3.0">
	 * ExcelExport.export(
	 * 						this.Areachart, 
	 * 						"exportacion_chart.xls", 
	 * 						new ExcelExportParams(
	 * 												[
	 * 												new ExcelExportParamValue("Month", "month"),
	 * 												new ExcelExportParamValue("Manufacturer", "manufacturer"),
	 * 												]
	 * 											)
	 * 						);
	 *  </listing>
	 * @example Exportacion de un dataProvider generico (arraycollection, array, etc)
	 * <listing version="3.0">
	 * ExcelExport.export(
	 * 						this.flat, 
	 * 						"exportacion_flatData.xls", 
	 * 						new ExcelExportParams(
	 * 												[
	 * 												new ExcelExportParamValue("Nombre", "nombre"),
	 * 												new ExcelExportParamValue("Apellido", "apellido"),
	 * 												]
	 * 											)
	 * 						);
	 * </listing>
	 * 
	 * 
	 */
	
	public class ExcelExport implements IExcelExport
	{
		//-----------------------------
		// Variables
		//-----------------------------
		/**
		 * 
		 * Fila de inicio por default desde donde se comienzan a agregar datos
		 * 
		 */
		public var defaultRowInit:int = 0;
		
		/**
		 * 
		 * Columna de inicio por defautl desde donde se comienzan a agregar datos
		 * 
		 */
		public var defaultColInit:int = 0;
		
		/**
		 * 
		 * Texto default a agregar a la celda cuando no se puede obtener su dato
		 * 
		 */
		public var defaultCellString:String = "";
		
		//-----------------------------
		// Constructor
		//-----------------------------
		public function ExcelExport()
		{
		}
		
		//-----------------------------
		// Public function
		//-----------------------------
		/**
		 * 
		 * Exporta los datos de un datagrid hacia un Excel.
		 * Toma el dataProvider del mismo y las columnas para su exportación.
		 * 
		 * @param grid				Referencia aun DataGrid, AdvancedDataGrid u OLAPDataGrid (lo que se va a exportar).
		 * @param defaultName		Nombre con el que se va a crear el archivo.
		 * @param params			Referencia a un objeto ExcelExportParams
		 * 
		 */
		public function fromGrid(grid:*, defaultName:String, params:ExcelExportParams = null):void 
		{
			if ( !(grid is DataGrid) && !(grid is AdvancedDataGrid) && !(grid is OLAPDataGrid) )
				return;
			
			if (grid == null || grid.dataProvider == null || defaultName == null || defaultName == "")
				throw new ArgumentError("ExcelExport.fromGrid. Error in arguments");
			
			var colsValues:Array = this.getColsValues(grid.columns, "headerText","dataField", params);

			if ( colsValues.length == 0 )
				return;
			
			var params:ExcelExportParams = (params)?params:new ExcelExportParams();
			params.extraDataAfter = [];
			params.extraDataBefore = [];
			params.colsValues = colsValues;
			this.fromObject(grid.dataProvider, defaultName, params);
		}
		
		/**
		 * 
		 * Exporta los datos de un gráfico de flex hacia un Excel.
		 * Toma el dataProvider del mismo y las series para su exportación.
		 *
		 * @param chart				Chart que se va a exportar
		 * @param defaultName		Nombre con el que se va a crear el archivo.
		 * @param params			Referencia a un objeto ExcelExportParams
		 *  
		 */
		public function fromChart(chart:ChartBase, defaultName:String, params:ExcelExportParams = null):void
		{
			if ( chart == null || chart.dataProvider == null || chart.dataProvider.length < 1 || 
				chart.series == null || chart.series.length < 1 || 
				defaultName == null || defaultName == "" )
				throw new ArgumentError("ExcelExport.fromChart. Error in arguments");

			var colsValues:Array = this.getColsValues(chart.series, "displayName", "yField", params);
			if ( colsValues.length == 0 )
				return;
	
			var params:ExcelExportParams = (params)?params:new ExcelExportParams();
			params.extraDataAfter = [];
			params.extraDataBefore = [];
			params.colsValues = colsValues;

			this.fromObject(chart.dataProvider, defaultName, params);
		}
		
		/**
		 * 
		 * Export to Excell from ArrayCollection, Array, etc
		 * 
		 * @param obj				Objeto a exportar XML, XMLList, ICollectionView o Array
		 * @param defaultName		Nombre con el que se va a crear el archivo.
		 * @param params			Referencia a un objeto ExcelExportParams
		 * 
		 */
		public function fromObject(obj:Object, defaultName:String, params:ExcelExportParams):void
		{
			if ( !(obj is XML) && !(obj is XMLList) && !(obj is XMLList) && !(obj is Array) && !(obj is ICollectionView) )
				throw new ArgumentError("ExcelExport.fromObject. Error in arguments");
			
			if ( params == null || params.colsValues == null || params.colsValues.length == 0 )
				return;

			
			params.colsValues = this.getColsValues([], "", "", params);
			params.extraDataAfter = [];
			params.extraDataBefore = [];
			
			var _dp:ICollectionView = this.getDataProviderCollection(obj);
			if ( _dp == null )
				return;
				
			this.exportToExcel(_dp, defaultName, params);
		}
		
		/**
		 * 
		 * Realiza la exportacion de datos a excell
		 * 
		 * @param obj				Objeto a exportar
		 * @param defaultName		Nombre con el que se va a crear el archivo.
		 * @param params			Referencia a un objeto ExcelExportParams
		 * 
		 */
		public function export(obj:Object, defaultName:String, params:ExcelExportParams = null):void
		{
			if (obj == null || defaultName == null || defaultName == "")
			{
				throw new ArgumentError("ExcelExport.export. Error in arguments");
			}
			else if ( obj is ChartBase )
			{
				this.fromChart(
							obj as ChartBase, 
							defaultName, 
							params
							);
			}
			else if (obj is DataGrid || obj is AdvancedDataGrid || obj is OLAPDataGrid)
			{
				this.fromGrid(
								obj, 
								defaultName, 
								params
								)
			} 
			else if (obj is XML || obj is XMLList || obj is Array || obj is ICollectionView)
			{
				if ( params )
				{
					this.fromObject(
									obj, 
									defaultName,
									params
									);
				}
			}
			
			return;
		}
		
		//-----------------------------
		// Protected function
		//-----------------------------
		/**
		 * 
		 * Genera el archivo excel a partir de una colección de datos.
		 * 
		 * @param obj				Objeto con los datos a exportar.
		 * @param defaultName		Nombre del archivo a exportar.
		 * @param params			Parametros de exportación.
		 * 
		 */
		protected function exportToExcel(obj:ICollectionView, defaultName:String, params:ExcelExportParams):void
		{
			if ( obj == null || params == null || params.colsValues == null || params.colsValues.length == 0)
				return;
			
			//completo los datos necesarios
			var rows:int = (params.rowInit)?params.rowInit:this.defaultRowInit;
			var cols:int = (params.colInit)?params.colInit:this.defaultColInit;;
			var cantCols:int = cols + params.colsValues.length;
			var cantRows:int = rows + obj.length + ((params.title && params.title != "")?1:0);
			var sheet:Sheet;
			var i:int;
			
			//obtengo la hoja del excel
			if ( params.sheet )
			{
				sheet = params.sheet;
			}
			else
			{
				sheet = new Sheet();
				sheet.resize(cantRows, cantCols);
			}
			
			//agrego el titulo
			if ( params.title && params.title != "" )
			{
				sheet.setCell(rows, cols, params.title);
				rows++;
			}
			
			//agrego los headers
			for ( i = cols; i < cantCols; i++)
			{
				if(params.colsValues[(i - cols)].hasOwnProperty("header")) {
					if (params.colsValues[(i - cols)].header) {
						sheet.setCell(rows, i, params.colsValues[(i - cols)].header);					
					}
				} else {
					if (params.colsValues[(i - cols)]) {
						sheet.setCell(rows, i, params.colsValues[(i - cols)]);					
					}
				}
			}
			
			//relleno con los datos
			//cols = 0;
			rows++;
			var cursor:IViewCursor = obj.createCursor();
			var cellValue:String;
			while ( !cursor.afterLast )
			{
				for (i = cols ; i < cantCols; i++)
				{
					
					if(params.colsValues[(i - cols)].hasOwnProperty("value")) {					
						if ((cursor.current as Object).hasOwnProperty(params.colsValues[(i - cols)].value) && 
								(cursor.current as Object)[params.colsValues[(i - cols)].value] != null )
							cellValue = (cursor.current as Object)[params.colsValues[(i - cols)].value];
						else
							cellValue = this.defaultCellString;
						sheet.setCell(rows, i, cellValue);
					} else {
						if ((cursor.current as Object).hasOwnProperty(params.colsValues[(i - cols)]) && 
							(cursor.current as Object)[params.colsValues[(i - cols)]] != null )
							cellValue = (cursor.current as Object)[params.colsValues[(i - cols)]];
						else
							cellValue = this.defaultCellString;
						sheet.setCell(rows, i, cellValue);
						
					}
				}
				
				rows++;
				cursor.moveNext();
			}
			
			//creo el archivo fisico de excel
			var xls:ExcelFile = new ExcelFile();
			xls.sheets.addItem(sheet);
			var bytes:ByteArray = xls.saveToByteArray();
			if ( bytes == null ) {
				Alert.show("El archivo esta vacío", "Atención", Alert.OK);
				return;
			}
			
			var remote:RemoteObject = new RemoteObject("fileService");
			// Nombre cableado
			var token:AsyncToken = remote.save(BrowserManager.getInstance().base, bytes);
	 		token.addResponder(new mx.rpc.Responder(onResultService, onFaultService));
		}
		
		private function onResultService(e:ResultEvent):void {
			// La url donde fue almacenado el archivo en el server la retorna en metodo save de fileService
			var url:String = e.result as String;
			var urlRequest:URLRequest = new URLRequest(url);
			navigateToURL(urlRequest, "_self");
		}
		
		private function onFaultService(e:FaultEvent):void {
			Alert.show("Error generando archivo " + e);
		}

		
		/**
		 * 
		 * A partir de un elemento pasado se genera un ICollectionView
		 * para su correcto recorrido
		 * 
		 * @param obj			Objeto a convertir a ICollectionView
		 * 
		 * 
		 * @return referencia a un ICollectionView. 
		 * 
		 */
		protected function getDataProviderCollection(obj:Object):ICollectionView
		{
			if ( (obj is Number && isNaN(obj as Number)) || (!(obj is Number) && obj == null))
			{
				return null;
			}
			else if ( obj is ICollectionView )
			{
				return obj as ICollectionView;
			}
			else if ( obj is Array )
			{
				return new ArrayCollection(obj as Array);
			}
			else if ( obj is XMLList )
			{
				return new XMLListCollection(obj as XMLList);
			}
			else if ( obj is XML )
			{
				var col:XMLListCollection = new XMLListCollection();
				col.addItem(obj);
				return col;
			}
			else if ( obj is Object )
			{
				return new ArrayCollection([obj]);
			}
			else
			{
				return null;
			}
		}
		
		/**
		 * 
		 * Genera la especificación de las columnas a exportar
		 * 
		 * @param dp					Listado a exportar
		 * @param headerName			Propiedad de los objetos de dp que contiene el texto del header
		 * @param valueName				Propiedad de los objetos de dp que contiene el valor de la celda
		 * @param extraColumsBefore		Listado de columnas a agregar antes de dp
		 * @param extraColumnsAfter		Listado de columnas a agregar luego de dp
		 * 
		 */
		protected function generateColsValues(dp:Array, headerName:String, valueName:String, extraColumsBefore:Array = null, extraColumnsAfter:Array = null):Array
		{
			var cant:Number = dp.length;
			var colsValues:Array = [];
			var fieldT:String;
			var headerT:String;
			
			for (var i:Number = 0; i < cant; i++)
			{
				if (dp[i].hasOwnProperty(headerName) && dp[i].hasOwnProperty(valueName))
				{
					headerT = dp[i][headerName];
					fieldT = dp[i][valueName];
					if ( fieldT == null || fieldT == "" || headerT == null || headerT == "")
						continue; 
					colsValues.push(new ExcelExportParamValue(headerT, fieldT));
				}
			}
			
			if ( extraColumsBefore && extraColumsBefore.length > 0 )
				colsValues = extraColumsBefore.concat(colsValues)
			if ( extraColumnsAfter && extraColumnsAfter.length > 0 )
				colsValues = colsValues.concat(extraColumnsAfter);
			
			return colsValues;
		}
		
		/**
		 * 
		 */
		protected function getColsValues(vec:Array, headerText:String, datafield:String, params:ExcelExportParams = null):Array
		{
			var extraSeriesBefore:Array = [];
			var extraSeriesAfter:Array = [];
			
			if ( params )
			{
				var colsValues2:Array = (params.colsValues)?params.colsValues:[];
				extraSeriesBefore = (params.extraDataBefore)?params.extraDataBefore:[];
				if ( extraSeriesBefore.length > 0 && colsValues2.length > 0 )
					extraSeriesBefore = extraSeriesAfter.concat(colsValues2);
				else if (extraSeriesBefore.length == 0 && colsValues2.length > 0 )
					extraSeriesBefore = colsValues2;
				extraSeriesAfter = (params.extraDataAfter)?params.extraDataAfter:[];
			}
			
			return this.generateColsValues(
											vec, 
											headerText, 
											datafield, 
											extraSeriesBefore, 
											extraSeriesAfter
											);
		}
	}
}